package org.simpleframework.aop;

import lombok.Getter;
import org.simpleframework.aop.annotation.Aspect;
import org.simpleframework.aop.annotation.Order;
import org.simpleframework.aop.aspect.AspectInfo;
import org.simpleframework.aop.aspect.DefaultAspect;
import org.simpleframework.core.BeanContainer;
import org.simpleframework.util.ValidationUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * AOP1.0实现 根据标记的注解进行织入
 *
 * @author: Oliver
 * @time: 2020/12/23/23:14
 */
@Getter
public class AspectWeaver {
    private BeanContainer beanContainer;

    public AspectWeaver() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 实现将切面织入到目标对象中生成代理对象并替换容器中的目标对象
     */
    public void doAop() {

        //首先获取到所有Aspect切面类集合
        Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
        if (ValidationUtil.isEmpty(aspectSet)) return;

        //  Set<Class<?>> ->list<AspectInfo>
        List<AspectInfo> aspectInfos = packageAspect(aspectSet);

        // 遍历容器中的class目标对象 --- 看是否有切面要织入,并将要织入的切面进行记录.
        Set<Class<?>> classes = beanContainer.getClasses();
        if (ValidationUtil.isEmpty(classes)) return;
        for (Class<?> targetClass : classes) {
            //排除自身Aspect类,即不能为切面织入切面
            if (targetClass.isAnnotationPresent(Aspect.class)) {
                continue;
            }
            // 粗筛获取到目标类要织入的List<AspectINfo>
            List<AspectInfo> roughMatchesAspectInfoList = roughMatchesAspectInfoList(targetClass, aspectInfos);

            //对目标对象进行织入切面,产生代理对象替换容器中原始对象
            wrapIfNecessary(roughMatchesAspectInfoList, targetClass);
        }


    }

    /**
     * 将切面转换成AspectInfo实例,并校验切面是否正确
     *
     * @param aspectSet 切面集合
     * @return 返回封装好的List<AspectInfo>
     */
    private List<AspectInfo> packageAspect(Set<Class<?>> aspectSet) {
        List<AspectInfo> aspectInfoList = new ArrayList<>();
        for (Class<?> c : aspectSet) {
            if (verifyAspect(c)) {
                Order order = c.getAnnotation(Order.class);
                Aspect aspect = c.getAnnotation(Aspect.class);
                String pointcut = aspect.pointcut();
                PointcutLocator locator = new PointcutLocator(pointcut);
                DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(c);
                aspectInfoList.add(new AspectInfo(defaultAspect, order.value(), locator));
            } else {
                //不遵守规范则直接抛出异常
                throw new RuntimeException("@Aspect and @Order must be added to the Aspect class, and Aspect class must extend from DefaultAspect");
            }
        }
        return aspectInfoList;
    }

    /**
     * 为目标对象尝试进行产生代理对象并替换目标对象
     *
     * @param roughMatchesAspectInfoList
     * @param targetClass
     */
    private void wrapIfNecessary(List<AspectInfo> roughMatchesAspectInfoList, Class<?> targetClass) {
        if (ValidationUtil.isEmpty(roughMatchesAspectInfoList)) {
            return;
        }
        Object proxy = ProxyCreator.createProxy(targetClass, new AspectListExecutor(targetClass, roughMatchesAspectInfoList));
        beanContainer.addBean(targetClass, proxy);
    }

    /**
     * aspectInfos 中是否有 该目标targetClass 织入切面对象,如果有放入粗筛集合中
     *
     * @param targetClass 目标Class 对象
     * @param aspectInfos 切面对象集合
     * @return 该目标类的粗筛结束后要织入的切面集合对象
     */
    private List<AspectInfo> roughMatchesAspectInfoList(Class<?> targetClass, List<AspectInfo> aspectInfos) {
        List<AspectInfo> roughMatchesAspectInfoList = new ArrayList<>();
        for (AspectInfo aspectInfo : aspectInfos) {
            PointcutLocator pointcutLocator = aspectInfo.getPointcutLocator();
            if (pointcutLocator.roughMatches(targetClass)) {
                roughMatchesAspectInfoList.add(aspectInfo);
            }
        }
        return roughMatchesAspectInfoList;
    }


    /**
     * 校验Aspect切面类是否否和要求  继承自DefaultAspect 且有Order Aspect标签  且 Aspect.value!=Aspect.class
     *
     * @param aspectClass 切面类
     * @return
     */
    private boolean verifyAspect(Class<?> aspectClass) {
        return aspectClass.isAnnotationPresent(Order.class)
                && aspectClass.isAnnotationPresent(Aspect.class)
                && DefaultAspect.class.isAssignableFrom(aspectClass);
    }

}
